<?php

namespace App\Services\User\Token;

use App\Services\User\Auth;
use App\Services\User\ValidateToken;
use Illuminate\Support\Facades\Redis;

/**
 * Class TokenLogic 用户token
 * @package App\Services\User
 */
class TokenLogic  {

    /**
     * token 生成
     * @param array $data
     * @return array
     * @throws \App\Exceptions\RequestFailException
     */
    public function create(array $data)
    {
        # 显式数据验证
        ValidateToken::checkValidate($data, ['id', 'create_ip', 'create_app_key']);

        # 业务逻辑
        $data['token'] = md5(mt_rand() . $data['create_ip'] . microtime());
        $data['create_time'] = NOW;
        // 给这个tocken默认角色
        $data['roles'] = isset($data['roles']) && $data['roles'] !== ''
            ? $data['roles'] . ',' . config('default_roles')
            : config('tapp.default_roles');
        $redis = app('redis');
        $key = TOKEN_PREFIX . $data['token'];
        // 设置哈希类型的数据
        // 存入最后续命时间
        $data['last_continue_life'] = NOW;
        $data['no_user_err_counts'] = 0;
        $res = $redis->hmset($key, $data);
        // 设置过期时间
        $redis->expire($key, TOKEN_EXPIRE);
        if($res){
            return auto_success($data, '成功创建一个令牌');
        } else {
            return auto_failure($data, '创建令牌失败');
        }
    }

//// 清理所有的token
//    public function clean(): array
//    {
//        $redis = redis();
//        $res = $redis->delete( $redis->keys(TOKEN_PREFIX . '*') );
//        if($res){
//            return auto_success($res, '成功清理所有的token');
//        } else {
//            return auto_failure($res, '清理所有的token失败');
//        }
//    }
    // 删除某一个token
    public static function delete(array $data): array
    {
        $redis = app('redis');
        $res = $redis->del( TOKEN_PREFIX . $data['token'] );
        if($res){
            return auto_success($res, '成功清理token');
        } else {
            return auto_failure($res, '清理token失败');
        }
    }
    // 获取token保存的内容
    public function hGetAll($token)
    {
        # 显式数据验证
        ValidateToken::checkValidate([$token], ['token']);

        $redis = app('redis');
        $key = TOKEN_PREFIX . $token;
        $res = $redis->hgetall($key);
        if($res && isset($res['id']) && $res['id'] && isset($res['last_continue_life']) && \bcsub(NOW, $res['last_continue_life']) > 60){
            //设置有效时间为 过期时间TOKEN_EXPIRE
            $redis->expire($key, TOKEN_EXPIRE);
            //修改该token内容中的“最后续命时间”
            $setData['token'] = $token;
            $setData['arr'] = ['last_continue_life'=>NOW];
            $this->hmset($setData);
        }
        return auto_return($res, '成功获取token内容', '获取token内容失败');
    }
//    //手动给某个token延时“过期时间”
//    public function continueLife($data): array
//    {
//        $redis = redis();
//        $key = TOKEN_PREFIX . $data['token'];
//        $res = $redis->hGetAll($key);
//        if(!isset($res['id']) && !$res['id']){
//            return verify_err(null, '获取该token中用户信息失败');
//        }
//        //修改该token内容中的“最后续命时间”
//        $setData['token'] = $data['token'];
//        $setData['arr'] = ['last_continue_life'=>NOW];
//        $this->hMset($setData);
//        $redis->expire($key, TOKEN_EXPIRE);
//        return auto_return($res, '成功给该token延时', '给该token延时失败');
//    }
    // 批量设置token要保存的内容
    public function hMset(array $data): array
    {
        $redis = app('redis');
        $res = $redis->hmset(TOKEN_PREFIX . $data['token'], $data['arr']);
        return auto_return($res, '成功保存内容至token', '保存内容至token失败');
    }
//    // 将某个token内保存的内容置空
//    public function empty(array $data): array
//    {
//        $token = TOKEN_PREFIX . $data['token'];
//        $redis = redis();
//        $keys = $redis->hKeys($token);
//        $diffKeys = array_diff($keys, ['create_ip','create_app_key','token','create_time']);
//        $count = count($diffKeys);
//        $sum = 0;
//        foreach($diffKeys as $k => $key){
//            $res = $redis->hDel($token, $key);
//            $res ?? $sum++;
//        }
//        return auto_return($count === $sum, '成功置空[' . $count . ']个字段', '置空token字段失败[' . $count . '-' . $sum . ']');
//    }

    // 检测某个token是否设置了某个变量key
    public function hExists(array $data, $redis = null): array
    {
        is_null($redis) && $redis = app('redis');
        $res = $redis->hexists(TOKEN_PREFIX . $data['token'], $data['key']);
        return auto_return($res, 'token中存在变量' . $data['key'], 'token中不存在变量' . $data['key']);
    }
    //根据token获取权限
    protected function _getAuthByToken(array $data, string $appendMsg = ''): array
    {
        $redis = app('redis');
        $existsRes = $this->hExists(['token'=>$data['token'], 'key'=>'roles'], $redis);
        if (SUCCESS_STATUS !== $existsRes['status']) {
            return token_failure($data['token'], 'token无效:' . $appendMsg);
        }
        // 读取该token对应的角色
        $rolesStr = $redis->hget(TOKEN_PREFIX . $data['token'], 'roles');
        if (!$rolesStr) {
            return limit_auth($rolesStr, '该token没有任何角色，故不可执行任何操作:' . $appendMsg);
        }

        // 根据该角色组字符串去读取对应的权限
        $authsRes = (new Auth())->getAuthsByRoles(['roles' => $rolesStr]);
        if(SUCCESS_STATUS !== $authsRes['status']){
            return limit_auth($authsRes['res'], '获取该角色的权限失败:' . $appendMsg);
        }
        return $authsRes;
    }

    /**
     * 根据token获取权限
     *
     * @param array $data
     * @param string $appendMsg
     * @return array
     * @throws \App\Exceptions\RequestFailException
     */
    public function getAuthByToken(array $data, string $appendMsg = ''): array
    {
        # 显式数据验证
        ValidateToken::checkValidate($data, ['token']);

        $redis = app('redis');
        $existsRes = $this->hExists(['token'=>$data['token'], 'key'=>'roles'], $redis);
        if (SUCCESS_STATUS !== $existsRes['status']) {
            return token_failure($data['token'], 'token无效:' . $appendMsg);
        }

        // 读取该token对应的角色
        $rolesStr = $redis->hget(TOKEN_PREFIX . $data['token'], 'roles');
        if (!$rolesStr) {
            return limit_auth($rolesStr, '该token没有任何角色，故不可执行任何操作:' . $appendMsg);
        }

        // 根据该角色组字符串去读取对应的权限
//        $authObj = logic('\app\serveruser\logic\Auth');
        $authsRes = (new Auth())->getAuthsByRoles(['roles' => $rolesStr]);
        if(SUCCESS_STATUS !== $authsRes['status']){
            return limit_auth($authsRes['res'], '获取该角色的权限失败:' . $appendMsg);
        }
        return $authsRes;
    }
//    // 对用户进行权限验证,应该单独放在redis的一个地方，有增加，删除，过期的方法，应该根据token去找角色，根据角色去找权限
//    // 每个token有对应的角色，在redis中，每个角色有对应的权限，还有角色合集，每个角色合集有对应的权限，
//    // 用户的角色合集去redids角色合集中验证
//    public function check(array $data): array
//    {
//        $authsRes = $this->_getAuthByToken($data, $data['auth']);
//        if($authsRes['status'] != SUCCESS_STATUS){
//            return $authsRes;
//        }
//        return in_array($data['auth'], $authsRes['res'])
//            ? auto_success(true, '成功对用户进行权限验证')
//            : limit_auth(false, '对用户进行权限验证失败:' . $data['auth']);
//    }
    // 用一个token验证多个权限是否通过
    public function checkAuths(array $data): array
    {
        array_walk($data['auths'], function (&$v) {
           $v = str_replace('\\', '/', $v);
        });
        $authsRes = $this->_getAuthByToken($data, implode(',', $data['auths']));
        if($authsRes['status'] != SUCCESS_STATUS){
            return $authsRes;
        }
        // 判断思路
        // 将需要判断的权限合并进原始数组中.去重复,再判断是否与原长度一致
        $oldLen = count(array_unique($authsRes['res']));
        $mergeAuthsRes = array_merge($authsRes['res'], $data['auths']);
        $newLen = count(array_unique($mergeAuthsRes));
        return $oldLen === $newLen
            ? auto_success(true, '成功对用户进行权限验证')
            : limit_auth(false, '对用户进行权限验证失败:' . implode(',', $data['auths']));
    }
    // 给一个token增加角色，保存在redis中，并返回增加后的角色
    public function addRoles(array $data): array
    {
        $redis = app('redis');
        // 读取原来保存的角色，
        $oldRolesStr = $this->_getRolesByToken($data['token'], $redis);
        // 默认角色数组
        $defaultRolesArr = explode(',', config('tpproject.default_roles'));

        // 解析成数组
        $oldRolesArr = explode(',', $oldRolesStr);
        // 将现在的角色角析成数组
        $addRolesArr = explode(',', $data['roles']);
        // 数组合并
        $newRolesArr = array_merge($defaultRolesArr, $oldRolesArr, $addRolesArr);
        // 数组去重
        $newRolesArr = array_unique($newRolesArr);
        // 数组排序
        sort($newRolesArr);
        // 转成逗号隔开
        $newRolesStr = implode(',', $newRolesArr);
        if ($oldRolesStr === $newRolesStr) {
            return auto_success($newRolesStr, '新增的角色与原角色一致');
        }
        // 保存
        $redis->hset(TOKEN_PREFIX . $data['token'], 'roles', trim($newRolesStr, ','));
        // 返回数据
        return auto_success($newRolesStr, '给token增加角色成功');
    }
//    // 给一个token减去角色,并返回余下的角色
//    public function subRoles(array $data): array
//    {
//        $redis = redis();
//        // 读取原来保存的角色，
//        $oldRolesStr = $this->_getRolesByToken($data['token'], $redis);
//        // 默认角色数组
//        $defaultRolesArr = explode(',', config('default_roles'));
//        // 解析成数组
//        $oldRolesArr = explode(',', $oldRolesStr);
//        // 数组合并
//        $mergeRolesArr = array_merge($defaultRolesArr, $oldRolesArr);
//        // 将现在的角色角析成数组
//        $subRolesArr = explode(',', $data['roles']);
//        // 取差集
//        $diffRolesArr = array_diff($mergeRolesArr, $subRolesArr);
//        // 数组去重
//        $newRolesArr = array_unique($diffRolesArr);
//        // 数组排序
//        sort($newRolesArr);
//        // 转成逗号隔开
//        $newRolesStr = implode(',', $newRolesArr);
//        if ($oldRolesStr === $newRolesStr) {
//            return null_data($newRolesStr, '去除角色后与原角色一致');
//        }
//        // 保存
//        $redis->hSet(TOKEN_PREFIX . $data['token'], 'roles', trim($newRolesStr, ','));
//        // 返回数据
//        return auto_success($newRolesStr, '给token去除角色成功');
//    }
    // 根据token获取用户的角色
    private function _getRolesByToken(string $token, $redis = null): string
    {
        $redis ?? $redis = app('redis');
        return $redis->hget(TOKEN_PREFIX . $token, 'roles');
    }
//    // 根据token获取用户的角色
//    public function getRoles(array $data): array
//    {
//        $res = $this->_getRolesByToken($data['token']);
//        return auto_return($res, '成功根据token获取用户的角色', '根据token获取用户的角色失败');
//    }
}